import utils from './utils';
import getOffsetParent from './getOffsetParent';
import isAncestorFixed from './isAncestorFixed';

/**
 * 获得元素的显示部分的区域
 * 该函数用于精确计算元素在页面中的可见区域矩形，它通过遍历元素的offsetParent链，结合容器溢出属性和视口边界，最终计算出元素可见的矩形区域。
 * 综合考虑：
 * 1. 祖先容器布局（overflow/scroll）
 * 2. 视口边界
 * 3. 文档尺寸
 * 4. 定位上下文（fixed定位容器）
 * 5. 浏览器特殊行为（如IE兼容）
 *
 * 返回的矩形表示在页面坐标中（相对于文档左上角）的可见区域。
 */
function getVisibleRectForElement(element, alwaysByViewport) {
  // 初始化可见区域矩形
  const visibleRect = {
    left: 0,
    right: Infinity,
    top: 0,
    bottom: Infinity,
  };

  // 获取元素的offsetParent并循环遍历
  let el = getOffsetParent(element);
  const doc = utils.getDocument(element);
  const win = doc.defaultView || doc.parentWindow;
  const body = doc.body;
  const documentElement = doc.documentElement;

  // Determine the size of the visible rect by climbing the dom accounting for
  // all scrollable containers.
  // 通过遍历DOM树并考虑所有可滚动容器来确定可见矩形区域
  while (el) {
    // clientWidth is zero for inline block elements in ie.
    // 首先排除一些特殊情况（如IE下的内联块元素、body和html元素，以及overflow为visible的元素）。因为只有当容器的overflow不为visible（即hidden、scroll、auto）时，才会限制子元素的可见区域。
    if (
      (navigator.userAgent.indexOf('MSIE') === -1 || el.clientWidth !== 0) &&
      // body may have overflow set on it, yet we still get the entire
      // viewport. In some browsers, el.offsetParent may be
      // document.documentElement, so check for that too.
      el !== body &&
      el !== documentElement &&
      utils.css(el, 'overflow') !== 'visible'
    ) {
      // 对于符合条件的容器，获取其位置（offset）并加上clientLeft/clientTop（考虑边框），然后更新可见矩形区域（visibleRect）的四个边界值，相当于将可见区域限制在该容器内部。
      const pos = utils.offset(el);
      // add border
      pos.left += el.clientLeft;
      pos.top += el.clientTop;

      // 更新四个边界，确保可见区域在容器内
      visibleRect.top = Math.max(visibleRect.top, pos.top);
      visibleRect.right = Math.min(
        visibleRect.right,
        // consider area without scrollBar
        pos.left + el.clientWidth,
      ); // 注意这里用clientWidth，即容器内容+内边距
      visibleRect.bottom = Math.min(
        visibleRect.bottom,
        pos.top + el.clientHeight,
      );
      visibleRect.left = Math.max(visibleRect.left, pos.left);
    } else if (el === body || el === documentElement) {
      // 如果遇到body或documentElement，则跳出循环，因为继续向上遍历没有意义（它们对应视口或文档）。
      break;
    }
    el = getOffsetParent(el);
  }

  // Set element position to fixed
  // make sure absolute element itself don't affect it's visible area
  // https://github.com/ant-design/ant-design/issues/7601
  // 将绝对定位（absolute）的元素改为固定定位（fixed），以避免该元素自身的位置影响其可见区域的计算
  let originalPosition = null;
  if (!utils.isWindow(element) && element.nodeType !== 9) {
    originalPosition = element.style.position;
    const position = utils.css(element, 'position');
    if (position === 'absolute') {
      element.style.position = 'fixed';
    }
  }

  // 获取视口和文档的尺寸以及滚动位置
  const scrollX = utils.getWindowScrollLeft(win);
  const scrollY = utils.getWindowScrollTop(win);
  const viewportWidth = utils.viewportWidth(win);
  const viewportHeight = utils.viewportHeight(win);
  let documentWidth = documentElement.scrollWidth;
  let documentHeight = documentElement.scrollHeight;

  // scrollXXX on html is sync with body which means overflow: hidden on body gets wrong scrollXXX.
  // We should cut this ourself.
  // 处理body的overflow:hidden情况
  const bodyStyle = window.getComputedStyle(body);
  if (bodyStyle.overflowX === 'hidden') {
    documentWidth = win.innerWidth;
  }
  if (bodyStyle.overflowY === 'hidden') {
    documentHeight = win.innerHeight;
  }

  // Reset element position after calculate the visible area
  // 恢复元素的position
  if (element.style) {
    element.style.position = originalPosition;
  }

  // 根据参数和祖先元素是否有fixed定位调整可见区域
  // 1. 如果参数alwaysByViewport为true，或者元素的祖先中有fixed定位的元素（通过isAncestorFixed检测），则相对于视口进行裁剪（即最终的可见区域不能超过视口边界）。
  if (alwaysByViewport || isAncestorFixed(element)) {
    // Clip by viewport's size.
    visibleRect.left = Math.max(visibleRect.left, scrollX);
    visibleRect.top = Math.max(visibleRect.top, scrollY);
    visibleRect.right = Math.min(visibleRect.right, scrollX + viewportWidth);
    visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + viewportHeight);
  } else {
    // 否则，相对于整个文档进行裁剪。但注意，文档的尺寸可能比视口大，也可能比视口小（比如文档内容不足一屏时），因此这里要取文档尺寸和当前视口加上滚动位置的最大值（即保证可见区域的边界至少是视口边界，但文档尺寸更大时则取文档边界）。
    // Clip by document's size.
    const maxVisibleWidth = Math.max(documentWidth, scrollX + viewportWidth);
    visibleRect.right = Math.min(visibleRect.right, maxVisibleWidth);

    const maxVisibleHeight = Math.max(documentHeight, scrollY + viewportHeight);
    visibleRect.bottom = Math.min(visibleRect.bottom, maxVisibleHeight);
  }

  // 检查计算出的矩形是否有效（左上角坐标非负，且宽高大于0）。如果不满足条件（即没有可见区域），返回null；否则返回矩形对象。
  return visibleRect.top >= 0 &&
    visibleRect.left >= 0 &&
    visibleRect.bottom > visibleRect.top &&
    visibleRect.right > visibleRect.left
    ? visibleRect
    : null;
}

export default getVisibleRectForElement;
